Topic 2 Multiprocessing (3)

Leedehai
Friday, April 21, 2017

2.5 Inter-process communication I: pipe

2.5.1 Pipe and pipe()

____ out p i p e in ____ | |╲ ───────────────────────────────── | |╲ | R └─| file ◀─── 0 1 0 1 1 0 1 0 1 ◀─── file | W └─| └──────┘(recv) ───────────────────────────────── (send) └──────┘
┌───────┬───────┐ pipe() ┌────────┬────────┐ int fds[2] │ │ │ ==> │read(R) │write(W)│ └[0]────┴[1]────┘ └[0]─────┴[1]─────┘
.PID 101.. .. .PID 102.. .. ┌───┬───┐ ┌───┬───┐ file : ┌───┬───────────┐ : │ R │ W │ │ R │ W │ desriptor │ │ : : │ └─┬─┴─┬─┘ └─┬─┴─┬─┘ tables : ┌─▼─┬─┴─┐ ┌─▼─┬───┐ : │ └──┬────│───┘ │ R │ W │ : : │ R │ W │ └─┬────│────┘ : └─▲─┴───┘ └─▲─┴─┬─┘ : │ │ │ : : │ │ ..─┬─▽──┬─▽──┬─.. file entry : └───────────────┴───┘ : │ <=pipe= │ table .. parent ... ... child ... ..─┴out─┴──in┴─.. (system-wide) two separate FD tables child shares file entries with parent

2.5.2 Redirect a file descriptor: dup2()

Pause and think for a while: fork(), waitpid(), execvp(), pipe(), dup2() - for each of them:
a) what does it do (in one sentence)?
b) does it affect the calling process's memory space? If so, how?
c) does it affect the calling process's FD table? If so, how?

In shell, you can use < and > to redirect standard input/output of a program that is called from a cammand line. For example:
$ /bin/sh < Command.txt
$ echo This repo is for CS110 > Readme.txt

2.5.3 An example: implementing subprocess()

Suppose we have an executive named sort, which reads in character strings (separated by newlines and ends by Ctrl+D) from its stdin and sorts those strings in an alphabetic order, and prints them to its stdout.

Now, we want sort to read hard-coded strings from a program driver, instead of from the console, and outputs the result to the console.

"subprocess" is just another term for "child process".

2.5.3.1 Analysis

An illustration of the pipe - it has four ends after fork()!

parent fds[1] (W) ─────────────────────▶ fds[0] (R) ──────────┐ ┌─────▶ <sealed> │ │ │ │ ┌────│────┘ │ │ ┌─▷ STDIN_FILENO │ │ │ child fds[1] (W) ────┘ └──────────▶ fds[0] (R) <sealed> ────────────────────▶ <closed>

An illustration of the processes:

execvp() "/bin/sh -c /usr/bin/sort" reaped subprocess ┌──●~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~●─ ─ ○ │ /bin/sh △ △ △ △ STDIN_FILENO(stdin) │ : : : via : || driver │ : : : pipe: fds[1] ────────────┴──────────●─●─●─...─●───────(w a i t)────────────▶ fork() words[i] EOF(Ctrl+D)

The full picture in the shell.

Entry in file entry table: process (FD value), ... console input: shell(0), driver(0), console output: shell(0), driver(1), /bin/sh(1), /usr/bin/sort(1) pipe's in file: driver(fds[1]) pipe's out file: /bin/sh(0), /usr/bin/sort(0)
┴ fork ● event ○ reap △ stdout (to console) : /usr/bin/sort process ┌++●**********************●*●- ○ │ △ △ △ △ stdin │ : : : : || /bin/sh process ┌~~~●+++┴+++:+:+:+++++:++||++(wait)+++++●─ ○ │ /bin/sh : : : : stdin │ : : : via : || │ : : : pipe: fds[1] driver ┌──●~~~┴~~~~~~~~~~~●~●~●~...~●~~~~~~~(w a i t)~~~~~●─ ○ │ driver words[i] EOF │ shell ───┴─────────────────────────(w a i t)────────────────────────▶

2.5.3.2 The code

/* main.c */ typedef struct { pid_t pid; /* the subprocess's pid */ int supplyfd; /* this FD is the writing-to end of the subprocess */ } subprocess_t; subprocess_t subprocess(const char *command); int main() { /* create a subprocess (i.e. child), * get the subprocess's pid and writing-to end */ subprocess_t sp = subprocess("/usr/bin/sort"); const char *words[] = { /* hard-coded words */ "felicity", "umbrage", "susurration", "halcyon", "pulchritude", "ablution", "somnolent", "indefatigable" }; /* write the words to the subprocess through the writing-to end */ for (int i = 0; i < sizeof(words); i++) { /* note that "%s" format of a char* is the content of the string, * not the value of the pointer itself. */ dprintf(sp.supplypid, "%s\n", words[i]); } /* close the writing-to end, * effectively sending a Ctrl+D (EOF) to the receiver */ close(sp.supplypid); /* wait on the child and reap it - don't make the child an orphan! */ int status; pid_t pid = waitpid(sp.pid, &status, 0); return pid == sp.pid && WIFEXITED(status) ? WEXITSTATUS(status) : -1; }

Another function. Note that a new function does NOT correspond to a new process.

/* subprocess.c */ typedef struct { pid_t pid; /* the subprocess's pid */ int supplyfd; /* this FD is the writing-to end of the subprocess */ } subprocess_t; subprocess_t subprocess(const char *command) { int fds[2]; pipe(fds); /* setting up a pipe */ /* create a subprocess, i.e. child process, * and construct a subprocess_t instance to return */ subprocess_t sp = {fork(); fds[1]}; if (sp.pid == 0) { /* for the child process */ /* prevent the child from writing to the pipe */ close(fds[1]); /* redirect the child's stdin FD to the pipe's reading end, and de-associate fd[0] from the reading end */ dup(fds[0], STDIN_FILENO); close(fds[0]); /* now, execute the intended program (/bin/sh) by execvp() */ /* since the called program inherits the caller process's FD table, * its stdin stream is connect to the pipe's reading end */ char *argv[] = {"/bin/sh", "-c", (char *) command, NULL}; execvp(argv[0], argv); /* if execvp() succeeds, then /bin/sh reads from the pipe, * getting the words published by the parent */ /* if execvp() succeeds, the child process won't reach this line */ } /* if execvp() succeeds, the child process won't reach this line */ close(fds[0]); /* prevent the parent from reading from the pipe */ return sp; }

Note that if execvp() succeeds, then the rest of the code is NOT executed, as the calling process is completely overwritten and rebooted by another program.

EOF